package com.lzj.common.security.service;

import cn.hutool.core.util.ObjectUtil;
import com.lzj.common.constant.CacheConstants;
import com.lzj.common.core.domain.dto.RoleDTO;
import com.lzj.common.core.domain.entity.SysUser;
import com.lzj.common.core.domain.model.LoginUser;
import com.lzj.common.core.service.PermissionService;
import com.lzj.common.core.service.UserService;
import com.lzj.common.enums.UserStatus;
import com.lzj.common.exception.user.UserException;
import com.lzj.common.exception.user.UserPasswordNotMatchException;
import com.lzj.common.exception.user.UserPasswordRetryLimitExceedException;
import com.lzj.common.helper.SecurityHelper;
import com.lzj.common.utils.BeanCopyUtils;
import com.lzj.common.utils.redis.RedisUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Optional;

/**
 * @author 贱贱
 * @Description 自定义Spring Security用户认证逻辑
 * @Date 2024/07/11 17:52
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class UserDetailServiceImpl implements UserDetailsService {

    private final UserService userService;
    private final PermissionService permissionService;
    private final PasswordEncoder passwordEncoder;
    
    @Value("${user.password.maxRetryCount}")
    private Integer maxRetryCount;

    @Value("${user.password.lockTime}")
    private Integer lockTime;
    
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = userService.selectUserByUserName(username);
        
        // 校验用户信息
        if (ObjectUtil.isNull(user)) {
            log.error("登录用户：{} 不存在.", username);
            throw new UserException("user.not.exists", username);
        }
        else if (UserStatus.DELETED.is(user.getStatus())){
            log.info("登录用户：{} 已注销.", username);
            throw new UserException("user.password.delete", username);
        }
        else if (UserStatus.DISABLE.is(user.getStatus())){
            log.info("登录用户：{} 已被停用.", username);
            throw new UserException("user.blocked", username);
        }
        
        // 校验密码
        validatePassword(user);
        
        LoginUser loginUser = new LoginUser();
        loginUser.setUserId(user.getUserId())
            .setUsername(username)
            .setPassword(user.getPassword())
            .setDeptId(user.getDeptId())
            .setDeptName(user.getDept().getDeptName())
            .setUserType(user.getUserType())
            .setRoles(BeanCopyUtils.copyList(user.getRoles(), RoleDTO.class))
            .setPermissions(permissionService.getMenuPermission(user));
        return loginUser;
    }

    /**
     * 验证用户密码
     * @param user 用户信息
     */
    public void validatePassword(SysUser user) {
        // 用户认证信息
        Authentication context = SecurityHelper.getAuthentication();
        String rawPassword = context.getCredentials().toString();
        
        //用户信息
        String username = user.getUserName();
        String password = user.getPassword();

        //用户输入密码的尝试次数
        int retryCount = (Integer) Optional.ofNullable(RedisUtils.getCacheObject(getPasswordCacheKey(username)))
            .orElse(0);
        
        if (retryCount >= maxRetryCount) {
            throw new UserPasswordRetryLimitExceedException(retryCount, lockTime);
        }
        // 验证失败
        if (!passwordEncoder.matches(rawPassword, password)){
            retryCount += 1;
            RedisUtils.setCacheObject(getPasswordCacheKey(username), retryCount, Duration.ofMinutes(lockTime));
            throw new UserPasswordNotMatchException();
        }else {
            clearLoginRecordCache(username);
        }
    }   
    
    private String getPasswordCacheKey(String username){
        return CacheConstants.PWD_ERR_CNT_KEY + username;
    }
    
    private void clearLoginRecordCache(String loginName) {
        if (RedisUtils.hasKey(getPasswordCacheKey(loginName)))
        {
            RedisUtils.deleteObject(getPasswordCacheKey(loginName));
        }
    }
    
}
